package com.ruoyi.framework.aspect;

import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.annotation.NoRepeatSubmit;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.enums.ExceptionEnum;
import com.ruoyi.common.exception.BizException;
import com.ruoyi.common.util.RedisUtil;
import com.ruoyi.common.util.ServletUtils;
import com.ruoyi.common.util.http.HttpHelper;
import com.ruoyi.common.util.request.RepeatedlyRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/** 防止表单重复提交切面
 *
 * @author LiuXiaoYu
 * @date 2021/10/14- 15:19
 */
@Aspect
@Component
@Slf4j
@Order(2)
public class NoRepeatSubmitAspect {

    @Value("${token.header}")
    private String header;

    @Value("${form.repeatParam}")
    private String repeatParam;

    @Value("${form.repeatTime}")
    private Long repeatTime;

    @Autowired
    private RedisUtil redisUtil;
    @Pointcut("@annotation(com.ruoyi.common.annotation.NoRepeatSubmit)")
    public void noRepeatSubmit(){

    }

    /**
     * 1.根据请求url路径以及请求的requestbody参数 与redis的进行比较
     * redis的保存为 map key为token的请求头+请求路径
     *   1.1若redis没有 说明10s已经过去了 即使表单数据相同也可以提交
     *      保存redis同时 还要插入数据库
     *   1.2 若redis存在 则比较参数是否相同 参数相同抛出异常 不同则继续
     * 2.
     * @param joinPoint
     */
    @Before("noRepeatSubmit()")
    public void handlerRepeatSubmit(JoinPoint joinPoint){
        //判断是否携带防表单重复提交注解
        NoRepeatSubmit noRepeatSubmit=getAnnotation(joinPoint);
        if (Objects.nonNull(noRepeatSubmit)){
            //说明携带了该注解 进行防重验证
            handleRepeatSubmitNow();
        }

    }

    /**
     *  /**
     *      * 1.根据请求url路径以及请求的requestbody参数 与redis的进行比较
     *      * redis的保存为 map key为token的请求头+请求路径
     *      *   1.1若redis没有 说明10s已经过去了 即使表单数据相同也可以提交
     *      *      保存redis同时 还要插入数据库
     *      *   1.2 若redis存在 则比较参数是否相同 参数相同抛出异常 不同则继续
     *      * 2.
     */
    private void handleRepeatSubmitNow() {
        String nowParams = "";
        HttpServletRequest request = ServletUtils.getRequest();
        //1.获取请求路径
        String requestUrl= request.getRequestURL().toString();
        //2.获取requestBody参数
        if (request instanceof RepeatedlyRequestWrapper) {
            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
        }
        // 3.body参数为空，获取Parameter的数据
        if (StringUtils.isEmpty(nowParams)) {
            nowParams = JSONObject.toJSONString(request.getParameterMap());
        }
        Map<String,Object> nowRepeatForm=new HashMap<>();
        nowRepeatForm.put(repeatParam,nowParams);

        // 唯一值(用token 但是有一个问题就是太长)
        String submitKey = request.getHeader(header);
        //TODO 没携带token 额根本不可能进来 待定
        if (StringUtils.isEmpty(submitKey)) {
            submitKey = requestUrl;
        }
        // 唯一标识（指定key + token值）
        String repeatKey= Constants.REPEAT_SUBMIT_KEY + submitKey;
        //从redis中获取是否已经存在
        Map<Object, Object> oldRepeatForm = redisUtil.hmget(repeatKey);
        //判断是否存在并且参数是否一致
        if (Objects.nonNull(oldRepeatForm)&&oldRepeatForm.size()>0){
            //说明是二次提交并且还在二次提交的时间间隔之内再次比对提交参数
            if (oldRepeatForm.containsKey(requestUrl)){
                Map<String, Object> preDataMap = (Map<String, Object>) oldRepeatForm.get(requestUrl);
                if (compareParams(nowRepeatForm, preDataMap)) {
                    //抛出异常 是重复提交
                    throw new BizException(ExceptionEnum.REPEAT_SUBMIT,"重复提交表单,请稍后再试");
                }
            }
        }
        Map<String,Object> redisRepeatForm=new HashMap<>();
        redisRepeatForm.put(requestUrl,nowRepeatForm);
        //存进redis 从配置文件获取时间间隔时间
        redisUtil.hmset(repeatKey,redisRepeatForm,repeatTime);
    }

    /**
     * 判断参数是否相同
     */
    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
        String nowParams = (String) nowMap.get(repeatParam);
        String preParams = (String) preMap.get(repeatParam);
        return nowParams.equals(preParams);
    }
    /**
     * 判断是否携带防止表单重复提交注解
     * @param joinPoint
     * @return
     */
    private NoRepeatSubmit getAnnotation(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature=(MethodSignature)signature;
        Method method = methodSignature.getMethod();
        if (Objects.nonNull(method)){
            return method.getAnnotation(NoRepeatSubmit.class);
        }
        return null;
    }
}
